package com.mybatis.boost.mapper.provider;

import com.google.common.collect.Lists;
import com.mybatis.boost.mapper.model.MapperException;
import com.mybatis.boost.mapper.util.EntityHelperExt;
import com.mybatis.boost.mapper.util.sql.SqlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.session.Configuration;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.EntityTable;
import tk.mybatis.mapper.mapperhelper.EntityHelper;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.mapper.mapperhelper.MapperTemplate;

import javax.annotation.Nullable;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static com.google.common.collect.Lists.newArrayList;
import static com.mybatis.boost.mapper.model.MapperConstants.GENERATED_JDBC;
import static com.mybatis.boost.mapper.util.sql.InsertSqlUtil.getSelectKeySql;
import static java.util.stream.Collectors.toList;
import static jodd.util.StringUtil.isBlank;
import static org.apache.ibatis.executor.keygen.SelectKeyGenerator.SELECT_KEY_SUFFIX;
import static org.apache.ibatis.mapping.SqlCommandType.SELECT;

/**
 * @author jearton
 * @since 2017/2/3
 */
@Slf4j
public abstract class SingleMapperTemplate extends MapperTemplate {

    private Map<String, Class<?>> entityClassMap = new HashMap<>();
    private Class<?> mapperClass;
    private MapperHelper mapperHelper;

    SingleMapperTemplate(Class<?> mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
        this.mapperClass = mapperClass;
        this.mapperHelper = mapperHelper;
    }

    @Override
    protected void newSelectKeyMappedStatement(MappedStatement ms, EntityColumn column) {
        Configuration config = ms.getConfiguration();
        String keyId = ms.getId() + SELECT_KEY_SUFFIX;
        if (config.hasKeyGenerator(keyId)) {
            return;
        }
        Class<?> entityClass = getEntityClass(ms);
        KeyGenerator keyGenerator;
        String generator = isBlank(column.getGenerator()) ? getIDENTITY() : column.getGenerator();
        if (generator.equalsIgnoreCase(GENERATED_JDBC)) {
            keyGenerator = new Jdbc3KeyGenerator();
        } else {
            SqlSource sqlSource = new DynamicSqlSource(config, getSelectKeySql(column, generator));

            MappedStatement.Builder statementBuilder = new MappedStatement.Builder(config, keyId, sqlSource, SELECT);
            statementBuilder.resource(ms.getResource());
            statementBuilder.fetchSize(null);
            statementBuilder.keyGenerator(new NoKeyGenerator());
            statementBuilder.keyProperty(column.getProperty());
            statementBuilder.keyColumn(null);
            statementBuilder.databaseId(null);
            statementBuilder.lang(config.getDefaultScriptingLanguageInstance());
            statementBuilder.resultOrdered(false);
            statementBuilder.resultSets(null);
            statementBuilder.timeout(config.getDefaultStatementTimeout());

            ParameterMap inlineParameterMap = new ParameterMap.Builder(config, keyId + "-Inline", entityClass,
                    newArrayList()).build();
            statementBuilder.parameterMap(inlineParameterMap);

            ResultMap inlineResultMap = new ResultMap.Builder(config, keyId + "-Inline", column.getJavaType(),
                    newArrayList(), null).build();
            statementBuilder.resultMaps(Lists.newArrayList(inlineResultMap));
            statementBuilder.resultSetType(null);

            statementBuilder.flushCacheRequired(false);
            statementBuilder.useCache(false);
            statementBuilder.cache(null);

            try {
                config.addMappedStatement(statementBuilder.build());
            } catch (Exception e) {
                //ignore
                log.error("addMappedStatement error", e);
            }

            keyGenerator = new SelectKeyGenerator(config.getMappedStatement(keyId, false), isBEFORE());
            try {
                config.addKeyGenerator(keyId, keyGenerator);
            } catch (Exception e) {
                //ignore
                log.error("addKeyGenerator error", e);
            }
        }

        //keyGenerator
        setKeyGenerator(ms, column, keyGenerator);
    }

    /**
     * 设置resultMap
     */
    @Override
    protected void setResultType(MappedStatement ms, Class<?> entityClass) {
        ResultMap resultMap = _getResultMap(ms.getConfiguration(), entityClass);
        List<ResultMap> resultMaps = Collections.unmodifiableList(newArrayList(resultMap));
        SystemMetaObject.forObject(ms).setValue("resultMaps", resultMaps);
    }

    @Nullable
    private ResultMap _getResultMap(Configuration config, Class<?> entityClass) {
        EntityTable entityTable = EntityHelper.getEntityTable(entityClass);
        ResultMap resultMap = entityTable.getResultMap(config);
        if (resultMap == null) {
            return null;
        }
        List<ResultMapping> resultMappings = resultMap.getResultMappings().stream()
                .filter(resultMapping -> resultMapping.getColumn() != null)
                .map(resultMapping -> {
                    String column = resultMapping.getColumn().trim();
                    // 去除首尾的分隔符，如果有的话
                    column = SqlUtil.removeDelimiter(column);
                    ResultMapping.Builder builder = new ResultMapping.Builder(config, resultMapping.getProperty(), column, resultMapping.getJavaType());
                    builder.jdbcType(resultMapping.getJdbcType());
                    builder.typeHandler(resultMapping.getTypeHandler());
                    builder.flags(resultMapping.getFlags());
                    return builder.build();
                })
                .collect(toList());
        return new ResultMap.Builder(config, "BaseMapperResultMap", entityClass, resultMappings, true).build();
    }

    @Override
    public Class<?> getEntityClass(MappedStatement ms) {
        String msId = ms.getId();
        if (entityClassMap.containsKey(msId)) {
            return entityClassMap.get(msId);
        } else {
            return Stream.of(getMapperClass(msId).getGenericInterfaces())
                    .filter(type -> type instanceof ParameterizedType)
                    .map(type -> (ParameterizedType) type)
                    .filter(t -> t.getRawType() == this.mapperClass || this.mapperClass.isAssignableFrom((Class<?>) t.getRawType()))
                    .findFirst()
                    .map(t -> {
                        Class<?> returnType = (Class<?>) t.getActualTypeArguments()[0];
                        //获取该类型后，第一次对该类型进行初始化
                        EntityHelperExt.initEntityTableMap(returnType, mapperHelper.getConfig());
                        entityClassMap.put(msId, returnType);
                        return returnType;
                    })
                    .orElseThrow(() -> new MapperException("无法获取Mapper<T>泛型类型:" + msId));
        }
    }

    void useGeneratedKeys(MappedStatement ms, EntityColumn column) {
        String keyId = ms.getId() + SELECT_KEY_SUFFIX;
        if (ms.getConfiguration().hasKeyGenerator(keyId)) {
            return;
        }
        String generator = isBlank(column.getGenerator()) ? getIDENTITY() : column.getGenerator();
        if (generator.equalsIgnoreCase(GENERATED_JDBC)) {
            KeyGenerator keyGenerator = new Jdbc3KeyGenerator();
            setKeyGenerator(ms, column, keyGenerator);
        }
    }

    private void setKeyGenerator(MappedStatement ms, EntityColumn column, KeyGenerator keyGenerator) {
        try {
            MetaObject msObject = SystemMetaObject.forObject(ms);
            msObject.setValue("keyGenerator", keyGenerator);
            msObject.setValue("keyProperties", column.getTable().getKeyProperties());
            msObject.setValue("keyColumns", column.getTable().getKeyColumns());
        } catch (Exception e) {
            //ignore
            log.error("keyGenerator error", e);
        }
    }

    /**
     * 根据msId获取接口类名称
     */
    public static String getMapperName(String msId) {
        if (!msId.contains(".")) {
            throw new MapperException("当前MappedStatement的id=" + msId + ",不符合MappedStatement的规则!");
        }
        return msId.substring(0, msId.lastIndexOf("."));
    }
}
